package com.slipper.core.file.local;

import com.slipper.core.file.IFileService;
import com.slipper.core.file.vo.FileVO;
import com.slipper.exception.CommonException;
import com.slipper.util.Tool;
import jakarta.servlet.http.HttpServletResponse;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.text.SimpleDateFormat;
import java.util.Date;

public class LocalFileService implements IFileService {
    private static final String MODE = "local";
    private final String saveMode;
    private final String savePath;

    public LocalFileService(String saveMode, String savePath) {
        this.saveMode = saveMode;
        this.savePath = savePath;
    }

    @Override
    public FileVO saveFile(String fileName, byte[] bytes) throws Exception {
        String relativePath = getRelativePath();
        String absolutePath = getAbsolutePath(relativePath);
        String saveName = getFileName() + getExtension(fileName);

        try(FileOutputStream fos = new FileOutputStream(absolutePath + saveName)) {
            fos.write(bytes);
            fos.flush();
        }

        FileVO fileVO = new FileVO();
        fileVO.setMode(MODE);
        fileVO.setName(Tool.isBlank(fileName) ? getFileName() : fileName);
        fileVO.setPath(relativePath + saveName);
        fileVO.setSize((long) bytes.length);
        return fileVO;
    }

    @Override
    public FileVO saveFile(File file) throws Exception {
        String relativePath = getRelativePath();
        String absolutePath = getAbsolutePath(relativePath);
        String saveName = getFileName() + getExtension(file.getName());

        Files.copy(Path.of(file.toURI()), Path.of(absolutePath + saveName), StandardCopyOption.REPLACE_EXISTING);

        FileVO fileVO = new FileVO();
        fileVO.setMode(MODE);
        fileVO.setName(file.getName());
        fileVO.setPath(relativePath + saveName);
        fileVO.setSize(file.getUsableSpace());
        return fileVO;
    }

    @Override
    public byte[] readFile(String path) throws Exception {
        File file = new File(getAbsolutePath(path));
        if (!file.exists()) throw new CommonException("附件不存在！");
        return Files.readAllBytes(file.toPath());
    }

    @Override
    public void readFile(String path, String rangeHeader, HttpServletResponse response) throws Exception {
        File file = new File(getAbsolutePath(path));
        long fileSize = file.length();
        long startPos = 0;
        long endPos = fileSize - 1;
        if (rangeHeader != null && rangeHeader.startsWith("bytes=")) {
            response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
            String numRange = rangeHeader.replaceAll("bytes=", "");
            if (numRange.startsWith("-")) {
                endPos = fileSize - Long.parseLong(numRange.substring(1));
            } else if (numRange.endsWith("-")) {
                startPos = Long.parseLong(numRange.substring(0, numRange.length() - 1));
                endPos = fileSize - 1;
            } else {
                String[] strRange = numRange.split("-");
                if (strRange.length == 2) {
                    startPos = Long.parseLong(strRange[0].trim());
                    endPos = Long.parseLong(strRange[1].trim());
                }
            }

            if (startPos < 0 || endPos < 0 || endPos >= fileSize || startPos > endPos) {
                response.setStatus(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE);
                return;
            }
        }

        response.setHeader("Accept-Ranges", "bytes");
        response.setHeader("Content-Type", "video/mov");
        response.setHeader("Content-Range", String.format("bytes %d-%d/%d", startPos, endPos, fileSize));
        response.setHeader("Content-Length", String.valueOf(endPos - startPos + 1));

        try (RandomAccessFile targetFile = new RandomAccessFile(file, "r")) {
            targetFile.seek(startPos);
            OutputStream outputStream = response.getOutputStream();

            byte[] cache = new byte[8 * 1024];
            int flag;
            while ((flag = targetFile.read(cache)) != -1) {
                outputStream.write(cache, 0, flag);
            }
            outputStream.flush();
            outputStream.close();
        }
    }

    @Override
    public void saveFileToPath(String path, byte[] bytes) throws Exception {
        String absolutePath = getAbsolutePath(path);
        createFolder(absolutePath, true);
        try(FileOutputStream fos = new FileOutputStream(absolutePath)) {
            fos.write(bytes);
            fos.flush();
        }
    }

    /**
     * 获取相对相对路径，判断路径是否存在，不存在则创建
     * @return 相对路径
     */
    public String getRelativePath() {
        String relative = getFilePath(this.saveMode);
        String absolutePath = getAbsolutePath(relative);
        createFolder(absolutePath, false);
        return relative;
    }

    private void createFolder(String path, boolean isFile) {
        File file = new File(path);
        file = isFile ? file.getParentFile() : file;
        if (!file.exists() && !file.mkdirs()) {
            throw new CommonException("文件夹创建失败！");
        }
    }

    /**
     * 根据相对路径转换成绝对路径
     * @param relativePath 相对路径
     * @return 绝对路径
     */
    public String getAbsolutePath(String relativePath) {
        String path = "";
        if (Tool.isNotBlank(this.savePath)) path += this.savePath + "/";
        if (Tool.isNotBlank(relativePath)) path += relativePath + "/";
        return path.replaceAll("/{2,}", "/");
    }

    /**
     * 获取文件路径
     * @param pathMode 文件路径模式
     */
    public static String getFilePath(String pathMode) {
        StringBuilder filePath = new StringBuilder("/");
        Date curDate = new Date();
        switch (Integer.parseInt(pathMode)) {
            case 0: // 每年
                filePath.append((new SimpleDateFormat("yyyy")).format(curDate)).append(File.separatorChar);
                break;
            case 1: // 每月
                filePath.append((new SimpleDateFormat("yyyyMM")).format(curDate)).append(File.separatorChar);
                break;
            case 2: // 每日
                filePath.append((new SimpleDateFormat("yyyyMMdd")).format(curDate)).append(File.separatorChar);
                break;
        }
        return filePath.toString();
    }
}
